在開始今天的 API CRUD 進度前,我要介紹一個近期使用的一個工具:OrbStack,快速順暢是我從 Docker desktop 跳過來的第一印象,毫無疑問的最佳替代品,如果你是使用 Mac 在開發有使用到 docker 的話,非常建議更換成 OrbStack。
在前面我們已經將環境的設定都趨於一致,所以在本地開發前就要開始設定一些參數,首先會遇到的就是環境變數。
這邊我使用的也是 GCP 生態系的工具 - Secret Manager。
GCP Secret Manager 是一個安全且方便的密鑰管理系統,可以用來存儲和管理各種敏感數據。它可以與 Cloud Build 整合,在本地開發時,只需要一段指令就可以很簡單的使用,不會有太多麻煩的整合過程。
# Ref: https://cloud.google.com/secret-manager/docs/create-secret-quickstart#create_a_secret_and_access_a_secret_version
gcloud secrets create api-local \
--replication-policy="automatic" \
--data-file=".env"
gcloud beta secrets versions access latest
--secret="api-local" > ./.env
這邊實際上是針對 api-local
新增一個修訂版,使用上跟版本控制的邏輯是類似的
gcloud secrets versions add api-local
--data-file=".env"
最後請記得,使用工具的同時要研究一下計費方式,可以參考到文件上的敘述
Note: You are billed for both enabled and disabled secret versions. You are not billed for secret versions that are in the destroyed state.
https://cloud.google.com/secret-manager/docs/add-secret-version#states
根據過往的經驗,schema 會隨進度推進而會有程度不一的變動,所以目前先將基本的 table 先規劃出來。
以下將比較重要的部分節錄於下:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('episodes', static function (Blueprint $table) {
$table->uuid('id');
$table->uuid('channel_id');
$table->string('guid');
$table->string('guid_hash', 64);
$table->string('title');
$table->jsonb('metadata');
$table->jsonb('stream_url');
$table->string('caption_path')->nullable();
$table->timestampRange('published_range');
$table->smallInteger('status')->default(0);
$table->timestamps();
$table->softDeletes();
$table->index(['channel_id', 'status', 'published_at']);
$table->unique(['guid_hash']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('episodes');
}
};
<?php
namespace App\Models;
use App\Models\Builder\CommonBuilder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Episode extends Base
{
use HasFactory;
protected $casts = [
'guid' => 'string',
'title' => 'string',
'duration' => 'integer',
'summary' => 'string',
'metadata' => 'array',
'stream_url' => 'array',
'caption' => 'array',
'published_at' => 'datetime',
'status' => 'integer',
];
public static function query(): EpisodeBuilder
{
return parent::query();
}
public function newEloquentBuilder($query): EpisodeBuilder
{
return new EpisodeBuilder($query);
}
public function channel(): BelongsTo
{
return $this->belongsTo(Channel::class);
}
}
<?php
namespace App\Models\Builder;
use BackedEnum;
use Illuminate\Database\Eloquent\Builder;
class EpisodeBuilder extends Builder
{
public function isPublished(): self
{
return $this->whereStatus(EpisodeStatus::published);
}
// From common trait in other php file
public function whereStatus(int|BackedEnum $status): self
{
if ($status instanceof BackedEnum) {
$status = $status->value;
}
return $this->where('status', $status);
}
public function whereChannelId($id): self
{
return $this->where('channel_id', $id);
}
}
<?php
namespace App\Enum;
enum EpisodeStatus :int
{
case pending = 0;
case published = 1;
case unpublished = 2;
}
就算是在開發時間緊迫的功能開發中,還是會依序將 migration
、model
、query builder
、model factory
與 http respurce
的定義設定完整。這樣可以有效的讓後面的開發上,可以有更快速的測試與驗證的效率,並且減少出錯的機率。
以上是這次實作的紀錄,如果有哪個部分需要解釋的部分,可以在留言處留下問題,我會盡量補充。